#include "amever.h"

struct server s;
extern int errno;

/* General functions */
int server_init(int argc, char **argv) {
	/* Init the first clock */
	amelog("Initing server.\n");
	struct timeval tv;
	gettimeofday(&tv, NULL);
	s.clock = (unsigned int)((tv.tv_sec*1000) + (tv.tv_usec/1000)); // Init the first clock

	/* Parse config file */
	memset(&(s.maps), 0, sizeof(s.maps));
	memset(&(s.config), 0, sizeof(s.config));

	if ((s.logfile = open("ame-server.log", O_WRONLY | O_CREAT | O_APPEND, 0664))<0) {
		server_shut("Couldn't open logfile, exiting.\n");
	}
	s.fds[s.logfile].type = FD_LOGFILE;
	int opts;
	if (argc >= 2)
		opts = readConf(argv[1]);
	else
		opts = readConf("conf/init.cf");

	s.ameQ = chain_setInit(0,0,0xFFFF);
	s.freeNodes = chain_setInit(0, 0, 0xFFFF);
	s.clients = chain_setInit(0, 0, MAX_CLIENTS);
	s.chars = chain_setInit(0, 0, MAX_CLIENTS);

	/* Defaults */
	if (!s.config.input_port)
		s.config.input_port = 44443;

	if (!s.config.input_ip[0]) {
		strcpy(s.config.input_ip, "0");
	}
	else {
		amelog("%d\n", s.config.input_ip[0]);
	}
	server_signalInit();

	if ((s.client_listener = network_serve(s.config.input_ip, s.config.input_port))<0) {
		server_shut("Failed opening clients' port.\n");
	}
	else { // ???
		server_registerFD(s.client_listener, FD_CLIENT_LISTENER, 0);
		amelog("Port %d [%d] open for clients.\n", s.config.input_port, s.client_listener);
	}

	s.select_timeout.tv_sec 	= 0;
	s.select_timeout.tv_usec 	= 0;

	server_sql_init();
	luaw_init();
	
	memset(s.buffy, 0, sizeof(s.buffy));
	if (argc >= 3)
		opts += readConf(argv[2]);
	else
		opts += readConf("conf/postinit.cf");
	
	// Now all the mobs
	{ 
		int i = 0;
		struct chain_node *noderator = 0;
		struct world_map *map = 0; 
		for (; s.maps[i].p != 0; i++)
		{
			map = (s.maps[i]).p;
			noderator = map->mobs->first; 
			while (noderator) {
				amelog("Adding Mob id %d\n", MOB(noderator->val)->id);
				qAdd(s.ameQ, s.clock+1000,qMobDo,MOB(noderator->val));
				noderator=noderator->next;
			}	
		}
	}
	return 0;
}

void  server_shut(char *message) {
	amelog("Shutting server.\n");
	int i;
	s.status.shutting = 1;
	for (i=s.highest_fd+1;i>0;i--) {
		if ( s.fds[i].type > FD_NONE) {
			if ( s.fds[i].type == FD_CLIENT ) {
				server_unregister(i);
			}
			else if (s.fds[i].type == FD_LOGFILE) {
				amelog("Shutting log file.\n");
				server_unregister(i);
				close(i);
			}
			else {
				server_unregister(i);
				close(i);
			}
		}
	}
	server_sql_shut();
	luaw_shut();
	if (message == 0)
		amelog("Good bye cruel world (shutting server) (last client parsed: %d).\n", s.current_fd);
	else
		amelog("%s\n", message);
	amelog("Shutting maps.\n");

	for (i=0;MAP(i);i++)
		map_shut(MAP(i));
	exit(EXIT_SUCCESS);
}

void server_setMaxFD(int i) {
	while (--i && s.fds[i].type==FD_NONE);
		s.highest_fd = i+1;
}

void server_setMaxFDAdded(int fd) {
	if ((fd+1) > s.highest_fd)
		s.highest_fd = fd+1;
}

int server_registerFD (int fd, int type, void *p) {
	if (fd > MAX_HANDLERS) {
		amelog("(!!)MAX_HANDLERS reached\n");
		return -1;
	}
	if (type == FD_CLIENT) {
		server_registerClient(fd);
	}
	amelog("Memsetting...\n");
	memset(&(s.fds[fd]),0,sizeof(s.fds[fd]));
	s.fds[fd].type = type;
	if (p != 0)
		s.fds[fd].p = p;
	server_setMaxFDAdded(fd);
	return fd;
}

int server_registerClient (int fd) {
	if (!s.clients) {
		chain_setInit(0,0,MAX_CLIENTS); //
	}
	else if (s.clients->len > s.clients->maxlen) {
		amelog("MaxLen reached, cannot init another client.\n");
		CLIENT(fd)->eof = 1;
		return -1;
	}
	return 0;
}

void server_unregisterClient(int fd) {
	if (CLIENT(fd))
		CHAIN_REM(CLIENT(fd)->global_hook);
}

void server_unregister(int fd) {
	if (s.fds[fd].type == FD_CLIENT && !s.status.shutting) {
		client_shut(CLIENT(fd));
		s.fds[fd].type = FD_TIMEWAIT;
		char *tmp = malloc(4); // freed in game.c:qShut
		*(int *)tmp = fd;
		qAdd(s.ameQ, s.clock + 5*1000, qCloseFd, tmp);
		// network_shutdown(fd);
		s.fds[fd].idle = 0;
		server_unregister(fd);
		return;
 	}

	// memset(&(s.fds[fd]),0,sizeof(s.fds[fd]));
	// Common ops

	s.fds[fd].type = FD_NONE;
	s.fds[fd].p = 0;
	s.fds[fd].idle = 0;

	if ((fd+1) >= s.highest_fd)
		server_setMaxFD(fd+1);
}

void server_mainLoop() {
	int res, i;
	struct timeval tv;
	fd_set read_ports, write_ports;
	fflush(0);
	while (!s.status.shutting) {
		FD_ZERO(&read_ports);
		FD_ZERO(&write_ports);
		for (i=3;i<=s.highest_fd+1;i++) {
			switch (s.fds[i].type) {
				case FD_CLIENT:
					if (CLIENT(i) && CLIENT(i)->wbuffer && CLIENT(i)->rbuffer) {
						if (!CLIENT(i)->eof)
							FD_SET(i, &read_ports);
						FD_SET(i, &write_ports);
					}
					break;
				case FD_CLIENT_LISTENER:
					FD_SET(i, &read_ports);
					break;
				default:
					break;
			}
		}
		res = select(s.highest_fd, &read_ports, &write_ports, 0, &s.select_timeout);

		if (res == 0 || (res == -1 && errno == EINTR)) {
			goto endOfMainLoop;
		}
		if (res < 0) {
			perror ("select()");
			exit (1);
		}
		
		for (s.current_fd=3;s.current_fd<=s.highest_fd+1;s.current_fd++) {
			if (s.status.shutting)
				break;
			if (s.current_fd == s.client_listener && FD_ISSET(s.current_fd, &read_ports)) {
					network_accept(s.current_fd);
					continue;
			}
			else if (s.fds[s.current_fd].type == FD_NONE)
				continue;
			else if ( s.fds[s.current_fd].type == FD_CLIENT && CLIENT(s.current_fd) != NULL ) {
				CLIENT(s.current_fd)->net.readable = !!FD_ISSET(s.current_fd, &read_ports);
				CLIENT(s.current_fd)->net.writable = !!FD_ISSET(s.current_fd, &write_ports);
				if (client_do( CLIENT(s.current_fd) )) {
					server_unregister(s.current_fd);
				}
			}
		}	
endOfMainLoop:	 
		parseQueue(s.ameQ);
		s.counter++;
		#ifdef MAINLOOP_SLEEP
		usleep(MAINLOOP_SLEEP);
		#endif
		gettimeofday(&tv, NULL);
		s.clock = (unsigned long long)((tv.tv_sec*1000) + (tv.tv_usec/1000));
	} // end of main loop while(1)
}

void server_signalProc(int sn) {
	if (sn == SIGTERM)
		amelog("SIGTERM caught, shutting everything down.\n");
	else if (sn == SIGINT)
		amelog("SIGINT caught, shutting everythign down\n");
	else if (sn == SIGSEGV)
		amelog("SIGSEGV caught... \n");

	if (!s.status.shutting)
		server_shut(0);
	else {
		amelog("Insisting?\n");
		abort();
	}
}

void server_signalInit () {
	amelog("Initing signals\n");
	#ifdef SIGPIPE
	if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
		amelog("Could not install signal handler for SIGPIPE.\n");
	#else
	amelog("Warning: Your system doesn't recognize SIGPIPE signal.\n");
	#endif

    if (signal(SIGTERM, server_signalProc) == SIG_ERR)
		amelog("Could not install signal handler for SIGTERM.\n");
	if (signal(SIGINT, server_signalProc) == SIG_ERR)
		amelog("Could not install signal handler for SIGINT.\n");
	/*
	if (signal(SIGSEGV, server_signalProc) == SIG_ERR)
		amelog("Could not install signal handler for SIGSEGV.\n");
	*/
	if (signal(SIGBUS, SIG_DFL) == SIG_ERR)
		amelog("Could not install signal handler for SIGBUS.\n");
	if (signal(SIGILL, SIG_DFL) == SIG_ERR)
		amelog("Could not install signal handler for SIGILL.\n");
	if (signal(SIGTRAP, SIG_DFL) == SIG_ERR)
		amelog("Could not install signal handler for SIGTRAP.\n");
	if (signal(SIGTRAP, SIG_DFL) == SIG_ERR)
		amelog("Could not install signal handler for SIGTRAP.\n");
}

int server_registerMap (const char *path, const char *name) {
	int i;
	for (i=0;i<=MAX_MAPS;i++)
		if (s.maps[i].p == 0)
			break;
	
	amelog("Setting to position %d\n", i);
	
	if (i==MAX_MAPS) {
		amelog("Failed to load \"%s\" map coz MAX_MAPS reached, sorry.\n", path);
		return 0;
	}
	char new_name[MAP_MAX_NAME];
	if (name == 0) {
		strcpy(new_name, "map_");
		new_name[5] = 0x30 + i;
		new_name[6] = '\0';
	}
	else {
		strcpy(new_name, name);
	}
	amelog("Trying to init %d...\n", i);	
	if ((s.maps[i].p = map_init(i, path, new_name)) == 0) {
		memset(s.maps+i, 0, sizeof(s.maps[0]));
		amelog("Map %d not inited\n", i);
		return 1;
	}
	amelog("%d map inited, hurrah\n", i);
	return 0;
}

int server_sql_init(void){
	mysql_init(&s.mysql_h);
	amelog("Connecting to MySQL...\n");
	if(!mysql_real_connect(&s.mysql_h, s.config.db_host, s.config.db_user, s.config.db_pass,
		s.config.db_name, 0, (char *)NULL, 0)) {
			amelog("... failure %s\n", mysql_error(&s.mysql_h));
			exit(1);
	}
	amelog(" success.\n");
	return 0;
}

void server_sql_shut(void){
	mysql_close(&s.mysql_h);
	amelog("Shutting MySQL connection.\n");
}

int main (int argc, char **argv) {
	server_init(argc, argv);
	server_mainLoop();
	server_shut(0);
	exit(EXIT_SUCCESS);
}
